﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using SPath = System.IO.Path;

namespace ArgusLib
{
	public enum FilesystemEntryTypes : byte
	{
		Directory,
		File
	}

	public class FilesystemPath
	{
		List<string> levels;

		public FilesystemEntryTypes Type { get; private set; }

		private FilesystemPath()
		{
			this.levels = new List<string>();
		}

		public static FilesystemPath Parse(string path)
		{
			if (path.LastIndexOf('.') > path.LastIndexOfAny(new char[] { SPath.DirectorySeparatorChar, SPath.AltDirectorySeparatorChar }))
				return FilesystemPath.Parse(path, FilesystemEntryTypes.File);
			else
				return FilesystemPath.Parse(path, FilesystemEntryTypes.Directory);
		}

		public static FilesystemPath Parse(string path, FilesystemEntryTypes type)
		{
			if (Enum.IsDefined(typeof(FilesystemEntryTypes), type) == true)
			{
				if (path.ContainsAny(SPath.GetInvalidPathChars()) == true)
					return null;
			}
			else
			{
				throw new ArgumentOutOfRangeException("type");
			}

			FilesystemPath RetVal = new FilesystemPath();
			path = SPath.GetFullPath(path);
			string[] levels = path.Split(SPath.VolumeSeparatorChar);
			if (levels.Length < 1 || levels.Length > 2)
				return null;

			RetVal.levels.Add(levels[0]);
			if (levels.Length < 2)
				return RetVal;

			if (RetVal.Append(levels[1], type) == false)
				return null;

			if (Directory.Exists(RetVal.Root) == false)
				return null;
			return RetVal;
		}

		public string Root
		{
			get
			{
				PlatformID platform = System.Environment.OSVersion.Platform;
				if (platform == PlatformID.MacOSX || platform == PlatformID.Unix)
					return this.levels[0] + SPath.VolumeSeparatorChar;
				else
					return this.levels[0] + SPath.VolumeSeparatorChar + SPath.DirectorySeparatorChar;
			}
		}

		public string Path { get { return this.GetParentPath(this.levels.Count); } }
		public int Depth { get { return this.levels.Count; } }

		public bool Exists
		{
			get
			{
				if (this.Type == FilesystemEntryTypes.Directory)
					return Directory.Exists(this.Path);
				else
					return File.Exists(this.Path);
			}
		}

		public string GetParentPath(int depth)
		{
			if (depth < 0 || depth > this.Depth)
				throw new ArgumentOutOfRangeException("depth");
			StringBuilder sb = new StringBuilder(this.Root);
			for (int i = 1; i < depth; i++)
				sb.Append(this.levels[i] + SPath.DirectorySeparatorChar);
			if (this.Type == FilesystemEntryTypes.File && depth == this.levels.Count)
				sb.Remove(sb.Length - 1, 1);
			return sb.ToString();
		}

		public bool MoveUp() { return this.MoveUp(1); }

		public bool MoveUp(int levels)
		{
			if (this.levels.Count < levels)
				return false;

			this.levels.RemoveRange(this.levels.Count - levels, levels);
			return true;
		}

		public bool Append(string relativePath, FilesystemEntryTypes type)
		{
			string[] levels = relativePath.Split(SPath.AltDirectorySeparatorChar, SPath.DirectorySeparatorChar);
			List<string> newLevels = new List<string>();
			if (string.IsNullOrEmpty(levels[0]) == false)
				newLevels.Add(levels[0]);

			for (int i = 1; i < levels.Length-1; i++)
			{
				if (string.IsNullOrEmpty(levels[i]) == true)
					return false;
				newLevels.Add(levels[i]);
			}

			if (type == FilesystemEntryTypes.File)
			{
				if (levels[levels.Length-1].ContainsAny(SPath.GetInvalidFileNameChars()) == true)
					return false;
				else
					newLevels.Add(levels[levels.Length-1]);
			}
			else if (string.IsNullOrEmpty(levels[levels.Length-1]) == false)
			{
				newLevels.Add(levels[levels.Length-1]);
			}

			this.levels.AddRange(newLevels);
			this.Type = type;
			return true;
		}

		public bool Append(string relativePath)
		{
			if (relativePath.LastIndexOf('.') > relativePath.LastIndexOfAny(new char[] { SPath.DirectorySeparatorChar, SPath.AltDirectorySeparatorChar }))
				return this.Append(relativePath, FilesystemEntryTypes.File);
			else
				return this.Append(relativePath, FilesystemEntryTypes.Directory);
		}

		public override string ToString()
		{
			return this.Path;
		}
	}
}
